#platform "uLCD-32PT_GFX2"
/*************************************************
* Filename: ECG.4dg
* Version   1.3
* Created: 2010/12/19
* Author: Greg Glenn
* Description: ElectroCardiogram for uLCD-32PT
*   Intended for use with front-end analog circuit found on
*     www.code.google.com/p/cardioshield
*   Requires uSD card holding these files:
*       ding.wav
*
* Application can be run without the front-end circuitry by
*  running it only in SIM mode
**************************************************/

#inherit "4DGL_16bitColours.fnc"
#MODE RUNFLASH

/*************************************************************************************
  The AD799X ADC chips come in a version-0 and version-1, where the difference
  is the LSB of the I2C address. See table in datasheet for I2C addresses. This code
  is for the version-0 of the AD7995 chip

  Change the FRONT_END contant to the version of the front-end circuit being used.
**************************************************************************************/
#CONST
 FRONT_END          1.3           // 1.1 = original circuit, 1.2 = integrator stabilized, 1.3 = battery monitor
 ADC_RESOLUTION     4096
 ADC_WRITE          0b01010000    // AD7995-0 address is 0101000X, AD7995-1 is 0101001X
 ADC_READ           0b01010001
 OFF_BUTTON_X          5          // Locations of buttons
 OFF_BUTTON_Y        215
 MODE_BUTTON_X       258
 MODE_BUTTON_Y       152
 SCALE_BUTTON_X      258
 SCALE_BUTTON_Y      184
 PAUSE_BUTTON_X      258
 PAUSE_BUTTON_Y      215
 SCREENCAP_BUTTON_X  195
 SCREENCAP_BUTTON_Y  152
 FILE_BUTTON_X       195
 FILE_BUTTON_Y       184
 RECORD_BUTTON_X     55
 RECORD_BUTTON_Y     184
 RECALL_BUTTON_X     125
 RECALL_BUTTON_Y     184
 LEFT_PAGE_BUTTON_X   70
 LEFT_PAGE_BUTTON_Y  173
 RIGHT_PAGE_BUTTON_X 170
 RIGHT_PAGE_BUTTON_Y 173
 PAGE_SLIDER_LENGTH   90
 PAGE_SLIDER_X1       75
 PAGE_SLIDER_Y1      158
 PAGE_SLIDER_X2      PAGE_SLIDER_X1+PAGE_SLIDER_LENGTH
 PAGE_SLIDER_Y2      170
 DATA_SAMPLE_TIMER          TIMER1      // Timer assignments
 BATTERY_MONITOR_TIMER      TIMER3
 SCREENSAVER_TIMER          TIMER2
 GRAPH_PLOT_COLOR           YELLOW      // Colors
 GRAPH_BORDER_COLOR         RED
 GRAPH_BACKGROUND_COLOR     BLACK
 GRAPH_GRID_COLOR           RED
 PANEL_COLOR                STEELBLUE
 PANEL_BORDER_COLOR         LIGHTBLUE
 HEART_ICON_COLOR           RED
 RTRIGGER_LEVEL_COLOR       DARKSLATEBLUE
 MODE_BUTTON_COLOR          GREEN
 TIMESCALE_BUTTON_COLOR     DARKBLUE
 PAUSE_BUTTON_COLOR         GOLD
 SCREENCAP_BUTTON_COLOR     ORCHID
 FILE_BUTTON_COLOR          DARKORANGE
 RECORD_BUTTON_COLOR        DARKBLUE
 RECALL_BUTTON_COLOR        DARKGREEN
 OFF_BUTTON_COLOR           CRIMSON
 PAGE_BUTTON_COLOR          GOLD
 PAGE_SLIDER_COLOR          GOLD
 PAGE_THUMB_COLOR           RECALL_BUTTON_COLOR
 GRAPH_MIN_X        3
 GRAPH_MIN_Y        11      // Graph boundaries
 GRAPH_MAX_X        310
 GRAPH_MAX_Y        138
 PANEL_BOTTOM_Y     239
 PANEL_HEIGHT       95
 GRAPH_Y_RANGE      (GRAPH_MAX_Y - GRAPH_MIN_Y)
 MIDLINE_Y          ((GRAPH_MAX_Y - GRAPH_MIN_Y)/2)+GRAPH_MIN_Y)
 GRAPH_SCALE_DIVISOR 20                     // Divisor to scale data to fit into graph area (20)
 GRAPH_Y_OFFSET     -70                     // Offset to center data into graph area -70
 SIGNAL_BUFFER_LEN  2500                    // Length of ring buffer holding data
 SAMPLE_INTERVAL    2                       // In milliseconds, 2ms = 500 samples/sec is minimum recommended rate
 BATTERY_INTERVAL   1000                    // Update battery level display every 10 seconds
 BATTERY_COUNTS_PER_TENTH_VOLT 122          // ADC value equal to .1 volt of battery measured by ADC at 1/3 voltage divider
 SAMPLES_PER_MINUTE (1000/SAMPLE_INTERVAL)*60
 STARTR             760/SAMPLE_INTERVAL;    // Parameters for simple simulated signal
 ENDR               STARTR + 20;            // 0 to ENDT is approx 1 second
 STARTT             960/SAMPLE_INTERVAL;    // to give pulse rate of 60 beats/minute
 ENDT               STARTT + 20;            // in simulation mode
 SCREENTIMEOUT      64000                   // Screen saver timeout in Milliseconds
 RATE_SAMPLES       5                       // Number of samples captured and averaged to get heartrate
 CONT_MODE          1                       // Modes
 RTRIG_MODE         2
 SIM_MODE           3
 RTRIG_REWIND       200                     // Amount to rewind pointer on rtrig
 RTRIG_LOOK_AHEAD   20                      // Points we check for R wave before graphing them
 SCREEN_HRES         320                    // Horizontal screen resolution (pixels)
 SCREEN_VRES         242                    // Vertical screen resolution (pixels)
 SCREEN_SAVE_MESSAGE   "Saving Screen.."
 RECORDING_MESSAGE     "Recording"
 RECALL_MESSAGE        "Showing File"
 ERR_RECORDING_MESSAGE "ERR Recording Data"
 MESSAGE_LINE       19
 MESSAGE_COLUMN     8
 MAX_FILES          10                  // File0 through File9 can be used to save data
 MAX_FILE_MINUTES   10                  // 10 minutes of data per file is the limit
 TRUE 1
 FALSE 0
#END

var adcConfigByte;                      // Used by initADC()
var state, x, y;                        // Used by touch code in main
var simIndex, simt1;                    // Used by simulation signal generator function
var mode;                               // Mode of display
var r1,r2;                              // MSB, LSB from ADC ch0 (signal)
var r3,r4;                              // MSB, LSB from ADC ch1 (battery monitor)
var timescale, timescaleIndex;          // Timescale setting
var signalBuffer[SIGNAL_BUFFER_LEN];    // Buffer to store ADC data
var rateBuffer[RATE_SAMPLES];           // Buffer of timestamps for computing pulse rate
var dataInIndex, dataOutIndex;          // Indices for ring buffer
var rateIndex;                          // Index to rate buffer
var graphX, graphY;                     // Current xy point to plot
var oldgraphX, oldgraphY;               // Last point plotted
var rTriggerLevel;                      // Minimum amplitude that indicates R wave
var rTriggered;                         // Flag indicating we are currently in an R wave
var rTriggerGraphY;                     // Where to draw Rtrigger level on graph
var paused;                             // 1= paused, 0=running
var pulseRate;                          // Averaged pulse rate
var lastRIndex;                         // Buffer index of last R wave seen
var sdPresent;                          // MicroSD card is present and mountable
var grab_enable;                        // Master grab enable
var grab_num;                           // Next avail increment of file name for screen grab
var grab_fname[7];                      // Screen grab filename
var OKtoRewind;                         // Toggle for RTrig mode to allow graph of entire wave

// Vars used in recording to files or displaying file data

var filename[6];                        // File name chosen on file button
var loadedFilename[6];                  // Name of currently loaded file
var currentFile;                        // Number of File to write data to, or recall data from
var fileHandle;                         // Handle of open file
var recordingToFile;                    // Flag set to indicate recording data to a file
var fileSlider;                         // Slider showing current position in recalled file
var filePointer;                        // Pointer of current word in recalled file
var pagePointer;                        // Which page of file we are displaying
var pages;                              // Pages in file we just recalled
var fileSizeHigh;                       // High byte of file size.
var fileSizeLow;                        // Low byte of file size.
var samplesPerPage;                     // Number of 2-byte samples on each graph page - depends on timescale
var sampleCounter;                      // Accumulates samples saved to file
var minutesCounter;                     // Accumulates minutes of data recorded to file

/******************************************
  Main routine
    Initialize everything.
    On each loop,
      Check for touch of any button.
      Graph any buffered data.
*******************************************/
func main()
    checkMemory();
    initSDCard();
    initParameters();
    initScreen();
    initADC();
    initSampleTimer();
    initBatteryMonitorTimer();
    repeat
        touch_Set(TOUCH_ENABLE);
        state := touch_Get(TOUCH_STATUS);       // get touchscreen status
        x := touch_Get(TOUCH_GETX);             // get touch x,y
        y := touch_Get(TOUCH_GETY);
        if(state == TOUCH_PRESSED)              // if user touches screen
            if ((x > OFF_BUTTON_X && (x < OFF_BUTTON_X + 40))
             && (y > OFF_BUTTON_Y && y < OFF_BUTTON_Y + 30))    // Off button touched?
                gfx_Set(CONTRAST,0);                            // Turn Display off
                sys_Sleep(0);                                   // Put processor to sleep
            endif
            if (((x > MODE_BUTTON_X ) && (x < MODE_BUTTON_X + 50))
             && ((y > MODE_BUTTON_Y -5) && y < MODE_BUTTON_Y + 30))                 // Mode button touched?
                changeMode();
            endif
            if (((x > SCALE_BUTTON_X ) && (x < (SCALE_BUTTON_X + 50)))
             && ((y > SCALE_BUTTON_Y -5) &&  y < (SCALE_BUTTON_Y + 30)))            // Scale button touched?
                changeTimescale();
            endif
            if ((x > PAUSE_BUTTON_X && (x < (PAUSE_BUTTON_X + 50)))
             && ((y > PAUSE_BUTTON_Y -2) &&  y < (PAUSE_BUTTON_Y + 30)))            // Pause button touched?
                togglePause();
            endif
            if ((x > SCREENCAP_BUTTON_X && (x < (SCREENCAP_BUTTON_X + 50)))
             && ((y > SCREENCAP_BUTTON_Y -5) &&  y < (SCREENCAP_BUTTON_Y + 30)))    // Screen capture button touched?
                saveScreenShot();
            endif
            if ((x > FILE_BUTTON_X && (x < (FILE_BUTTON_X + 50)))
             && ((y > FILE_BUTTON_Y -5) &&  y < (FILE_BUTTON_Y + 30)))              // File button touched?
                rotateFile();
            endif
            if ((x > RECORD_BUTTON_X && (x < (RECORD_BUTTON_X + 50)))
             && ((y > RECORD_BUTTON_Y -5) &&  y < (RECORD_BUTTON_Y + 30)))          // Record button touched?
                toggleRecording();
            endif
            if ((x > RECALL_BUTTON_X && (x < (RECALL_BUTTON_X + 50)))
             && ((y > RECALL_BUTTON_Y -5) &&  y < (RECALL_BUTTON_Y + 30)))          // Recall button touched?
                recallFromFile();
            endif
            if ((x > LEFT_PAGE_BUTTON_X-20 && (x < (LEFT_PAGE_BUTTON_X + 2)))
             && ((y > LEFT_PAGE_BUTTON_Y-20 ) &&  y < (LEFT_PAGE_BUTTON_Y + 7)))    // Left page button touched?
                pageBack();
            endif
            if ((x > RIGHT_PAGE_BUTTON_X-2 && (x < (RIGHT_PAGE_BUTTON_X + 20)))
             && ((y > RIGHT_PAGE_BUTTON_Y-20 ) &&  y < (RIGHT_PAGE_BUTTON_Y + 7)))  // Right page button touched?
                pageForward();
            endif
            if ((x > PAGE_SLIDER_X1 && (x < (PAGE_SLIDER_X2)))
             && ((y > PAGE_SLIDER_Y1-5 ) &&  y < (PAGE_SLIDER_Y2 + 5)))             // Slider touched?
              pagePointer := MAX(1,updateFileSlider(pages, -x));                    // Go to appropriate page of file
              if(paused) displayPage();                                             // Display the page
            endif
            while (touch_Get(TOUCH_STATUS) != TOUCH_RELEASED);                      // wait for touch release
        endif
        graphBufferedData();                                                        // Plot any unplotted data
    forever
endfunc

/*************************************
  Start/Stop Recording to file
**************************************/
func toggleRecording()
    if(!recordingToFile)
        file_Erase(str_Ptr(filename));                      // Erases the chosen file
        fileHandle := file_Open(str_Ptr(filename), 'w');
        if(fileHandle == 0)
            txt_MoveCursor(MESSAGE_LINE,MESSAGE_COLUMN);
            setMessage("File not found");
            recordingToFile := FALSE;
        else
            gfx_Button(DOWN, RECORD_BUTTON_X, RECORD_BUTTON_Y, RECORD_BUTTON_COLOR, BLACK, FONT3, 1, 1, " STOP");
            pause(70);
            gfx_Button(UP, RECORD_BUTTON_X, RECORD_BUTTON_Y, RECORD_BUTTON_COLOR, WHITE, FONT3, 1, 1, " STOP");
            setMessage(RECORDING_MESSAGE);
            recordingToFile := TRUE;
            filePointer := 0;
            sampleCounter := 0;
            minutesCounter := 0;
        endif
    else
        gfx_Button(DOWN, RECORD_BUTTON_X, RECORD_BUTTON_Y, RECORD_BUTTON_COLOR, BLACK, FONT3, 1, 1, "RECORD");
        pause(70);
        gfx_Button(UP, RECORD_BUTTON_X, RECORD_BUTTON_Y, RECORD_BUTTON_COLOR, WHITE, FONT3, 1, 1, "RECORD");
        file_Close(fileHandle);
        fileHandle := 0;
        setMessage("Done.");
        pause(1000);
        clearMessage();
        recordingToFile := FALSE;
    endif
endfunc

/********************************************************
   Open file and display first page of data

   A page consists of the amount of data that can
     be displayed on the graph, which depends upon
     the currently selected timescale value
*********************************************************/
func recallFromFile()
    if(!recordingToFile)
        recallButtonFlash();
        initGraph();
        setMessage(RECALL_MESSAGE);
        recordingToFile := FALSE;
        if(!paused) togglePause();
        if(fileHandle)                      // Close any open file
            if(!file_Close(fileHandle))
                setMessage("ERR closing file");
                return;
            endif
            fileHandle := 0;
        endif
        fileHandle := file_Open(str_Ptr(filename), 'r'); // Open the file
        if(file_Error() != 0)
            setMessage("File not found");
            return;
        endif

        // Compute pages of data in the file and
        //  show the position in the file on the slider bar
        to(loadedFilename);
        print([STR]filename);
        to(TEXT);
        file_Size(fileHandle, &fileSizeHigh, &fileSizeLow);
        mode := 1;  // Switch to continous mode, rtrig mode will prevent full display
        showMode();
        showHeartWithBeep();
        updatePages(timescale);
   endif
endfunc

/*********************************************************************
  Update the number of pages in the file based on current timescale

    If timescale is changed while data is being displayed from a file,
     the graph will go back to page 1 of the data
*********************************************************************/
func updatePages(var oldTimescale)
    samplesPerPage := (timescale *2)* (GRAPH_MAX_X - GRAPH_MIN_X);
    pages := getPages(fileSizeHigh, fileSizeLow, samplesPerPage);
    filePointer := 0;
    pagePointer := 1;
    if(paused) displayPage();  // If not paused, it means we were displaying live data,
                               //  so we don't want to show any file data
endfunc

/*****************************************
 Compute number of pages in a file
  from high and low bytes of the file size
******************************************/
func getPages(var dividendHigh, var dividendLow, var divisor)
    var highResult;
    var lowResult;
    var result;
    // Shift both words right to divide by 2, then get rid of sign bit
    //  otherwise positive unsigned values with highest bit set will be interpreted
    //  as negatives
    highResult := (32767/(divisor >> 1) * ((dividendHigh >> 1) & 0b0111111111111111));
    lowResult  := ((dividendLow >> 1) & 0b0111111111111111)/divisor;
    result := (highResult + lowResult) ;
    return result;
endfunc

/********************************************************
  Graph a page of data using current pagePointer
********************************************************/
func displayPage()
    var point;
    clearMessage();
    initGraph();
    clearRateBuffer();  //Clear buffer so we can compute heartrate just for this page
    file_Index(fileHandle,0,(samplesPerPage*2),pagePointer-1);   // Go to start of page
        repeat
             point := file_GetW(fileHandle);
             if(file_Error() != 0)          // If reached end of file, do nothing since
                break;                      // the message area will already show we on the last page
             endif
             graphFromFile(point);
             filePointer++;
             if(graphX == GRAPH_MIN_X)        // Wrapped, so stop
                //graphX++;                   // Bump x so it will continue next time
                break;
             endif
        forever
        clearMessage();
        txt_MoveCursor(MESSAGE_LINE,MESSAGE_COLUMN);
        print([STR]loadedFilename, " page ",pagePointer," of ", pages);  // Show what page we are on
        updateFileSlider(pages, pagePointer);   // Show relative location in file on slider
        showPulseRate();                        // Update pulse rate
 endfunc

/********************************************************
  Go back one page and graph its data
    if we are paused and not already on page 1
********************************************************/
func pageBack()
    leftPageButtonFlash();
    if(paused && pagePointer > 1)
        pagePointer--;
        if(pagePointer < 1) pagePointer := 1;
        displayPage();
    endif
endfunc

/********************************************************
  Go forward one page and graph its data
    if we are paused and not already on last page
********************************************************/
func pageForward()
    rightPageButtonFlash();
    if(paused && pagePointer < pages)
        pagePointer++;
        displayPage();
    endif
endfunc

/******************************************************************
 Record a data point to file if enabled
   Track total minutes of data and limit file to MAX_FILE_MINUTES
******************************************************************/
func recordToFile(var value)
    if(recordingToFile)
        if(minutesCounter == MAX_FILE_MINUTES)
            setMessage("Record Limit Reached");
            pause(2000);
            toggleRecording();
        else
            if(!file_PutW(value, fileHandle)) setMessage(ERR_RECORDING_MESSAGE);
            sampleCounter++;
            if(sampleCounter == SAMPLES_PER_MINUTE)
                sampleCounter := 0;
                minutesCounter++;
            endif
        endif
    endif
endfunc

/*************************************
  Show how much memory is available
**************************************/
func checkMemory()
    gfx_Set(SCREEN_MODE,LANDSCAPE_R);
    txt_Set(FONT_SIZE, FONT1);
    txt_Set(TEXT_COLOUR, YELLOW);
    txt_Set(FONT_SIZE, FONT3);
    print("MEM[] used = ", (&sdPresent+2 - &x)*2,"\n"); // Show the 'gap' between your apps variables,
    print("max heap = ", mem_Heap(),"\n");              //  giving the size in bytes which is MEMSIZE* 2
    pause(3000);
endfunc

/*********************************************
 Try five times to mount the microSD card
    If mount fails, low heap may be to blame
*********************************************/
func initSDCard()
    var i;
    snd_Volume(127);    // Set volume
    snd_BufSize(0);
    sdPresent := 0;     // Default to microSD card not present
    i := 0;
    putstr("Mounting uSD drive...\n");
        pause(500);
    if (!file_Mount())
        repeat
        i++;
            if(!file_Mount())
                putstr("Drive not mounted...\n");
                pause(1000);
            else
                putstr("Mounted.\n");
                sdPresent := 1;     // microSD card is present
                break;
            endif
        until (i > 2);
    else
        putstr("Mounted.\n");
        sdPresent := 1;             // microSD card is present
    endif
    pause(1000);                    // Give user time to read

endfunc

/*********************************************************
 GraphBufferedData
   Called as an event on the graph timer.
   Graphs a data point from the buffer,
   does R-wave processing and recording when appropriate
*********************************************************/
func graphBufferedData()
    var scaledY;
    if(!paused && (dataOutIndex != dataOutStopPoint()))
        oldgraphX := graphX;                // Save last point so we can draw line to it
        oldgraphY := graphY;                //  from the current point
        graphY := GRAPH_MAX_Y - signalBuffer[dataOutIndex++];
        if(dataOutIndex == SIGNAL_BUFFER_LEN)
            dataOutIndex := 0;              // Wrap to beginning
        endif
        graphPoint();
        beatHeart(rTriggerLevel, r1);  // Do R-wave-triggered processing if data > rTriggerLevel
        recordToFile(graphY);
    endif
endfunc

/*****************************************
  Graph a data point pulled from a file
*****************************************/
func graphFromFile(var point)
    if(graphX == GRAPH_MIN_X+1) graphY := point;  // Start plotting at first point
    oldgraphX := graphX;
    oldgraphY := graphY;
    graphY := point;
    graphPoint();
    bumpDataIndexes();                  // needed for computation of heart rate in beatHeart()
    beatHeart(graphY, rTriggerGraphY);  // Do R-wave-triggered processing if point is above Y trigger level
endfunc

/**********************************************
  Increments the data indices
    Needed for computation of heart rate in
      beatHeart() when data is from a file
**********************************************/
func bumpDataIndexes()
    dataOutIndex++;
    if(dataOutIndex == SIGNAL_BUFFER_LEN) dataOutIndex := 0;
    dataInIndex++;
    if(dataInIndex == SIGNAL_BUFFER_LEN) dataInIndex := 0;
endfunc

/*************************************************
  Graph a single data point, regardless of source
**************************************************/
func graphPoint()

        if(graphX == GRAPH_MIN_X)
            oldgraphX := graphX;      // Don't draw line at leftmost point
            oldgraphY := graphY;
        endif

        if(graphY > GRAPH_MAX_Y)  graphY := GRAPH_MAX_Y;              // Limit to inside of graph area
        if(graphY < GRAPH_MIN_Y)  graphY := GRAPH_MIN_Y;
        if(graphX != GRAPH_MIN_X)  gfx_Line(graphX,graphY,oldgraphX,oldgraphY,GRAPH_PLOT_COLOR);

        if(--timescaleIndex == 0)           //  Increment x for every timescale*2 points on the x axis.
           timescaleIndex := timescale * 2; //  TS1 => 2 samples per x,  TS2 => 4 samples per x, TS3 => 6, TS4 => 8
           graphX++;                        //  614, 1228, 1842, 2456 samples per page
           clearPreviousPoint();
           drawRtrigLevelPoint();
        endif
        if(graphX >= GRAPH_MAX_X)     //Increment time tick for X axis
            graphX := GRAPH_MIN_X;
            clearPreviousPoint();
        endif

endfunc


/***************************************************
  Plot a line showing where the Rtrigger level is.
***************************************************/
func drawRtrigLevelPoint()
   if(mode == RTRIG_MODE)
        gfx_PutPixel(graphX, rTriggerGraphY, RTRIGGER_LEVEL_COLOR);
   endif
endfunc

/****************************************************************
 Restore background to how it was before a point was plotted
*****************************************************************/
func clearPreviousPoint()
    gfx_RectangleFilled(graphX,GRAPH_MIN_Y,graphX+3,GRAPH_MAX_Y, GRAPH_BACKGROUND_COLOR);
endfunc

/**************************************************
  Clear the buffer used to compute the heart rate
**************************************************/
func clearRateBuffer()
var i;
    for (i := 0; i<RATE_SAMPLES; i++)
        rateBuffer[i] := 0;
    next
    lastRIndex := -1;
endfunc

/****************************************************************
  Determine point in data buffer to stop graphing so that the
   lookahead can work (see beatHeart())
****************************************************************/
func dataOutStopPoint()
    var margin;
    margin := dataInIndex - RTRIG_LOOK_AHEAD;
    if(margin >= 0)
        return margin;
    else
        return (SIGNAL_BUFFER_LEN + margin);
    endif
endfunc


/***************************************************************
    If R wave goes above the rTriggerLevel
       Show heart icon with beep
       Record index in rate buffer
       Compute pulse rate using average of 5 pulses

       Hide the heart icon when level goes below trigger level
****************************************************************/
func beatHeart(var value, var triggerLevel)
    var index, interval, goodSamples;
    if((value < triggerLevel) && !rTriggered)
        if(!paused) showHeartWithBeep();   // If paused, we may be displaying a file, so don't show icon

        //  Accumulate five R-to-R time intervals in the rateBuffer,
        //    and compute average pulse rate each time another
        //   R wave comes in

        if(lastRIndex > -1)  //Dont start recording intervals until after first trigger has happened
            interval := dataOutIndex - lastRIndex;
            if(interval >= 0)
                rateBuffer[rateIndex] := interval;
            else
                rateBuffer[rateIndex] := (SIGNAL_BUFFER_LEN - lastRIndex) + dataOutIndex;
            endif
            rateIndex++;
            if(rateIndex >= RATE_SAMPLES)
                rateIndex := 0;
            endif
        endif

        rTriggered := 1;            // Note that we have encountered an R-wave
        lastRIndex := dataOutIndex;
        index := 0;
        pulseRate := 0;
        goodSamples := 0;
        while (index < RATE_SAMPLES)
            if(rateBuffer[index] != 0) goodSamples++;   // Note the number of non-zero samples
            pulseRate := pulseRate + rateBuffer[index]; // Sum the non-zero samples
            index++;
        wend

        // Find beats per minute, and display it by averaging the non-zero samples

        if(goodSamples > 0)
            pulseRate := ((1000 / SAMPLE_INTERVAL) * 60 / (pulseRate / goodSamples));
        endif
        //if(!paused) showPulseRate();

        //   Reset to beginning of graph and rewind buffer pointer some,
        //    if we are in Rtrig mode, so the data leading up to the Rwave will be displayed

        if(mode == RTRIG_MODE)
            graphX := GRAPH_MAX_X;
            if((dataOutIndex - RTRIG_REWIND) >= 0)
                dataOutIndex := dataOutIndex - RTRIG_REWIND;
            else
                dataOutIndex := dataOutIndex - RTRIG_REWIND + SIGNAL_BUFFER_LEN;
            endif
        endif
    endif
    if((value > triggerLevel) && rTriggered)    // Note that we have reached the end of the R wave
        if(!paused)
            hideHeart();
        endif
        showPulseRate();
        rTriggered := 0;
    endif
endfunc

func showPulseRate()
    txt_Set(FONT_SIZE, FONT3);
    if(paused)              // Erase previous pulserate with opaque panel color
        txt_Set(TEXT_OPACITY, OPAQUE);
        txt_Set(TEXT_HIGHLIGHT, PANEL_COLOR);
        txt_MoveCursor(13,2);
        print("    ");
        showHeart();        // Display heart icon
        txt_Set(TEXT_OPACITY, TRANSPARENT);
        txt_Set(TEXT_COLOUR, CYAN);
        txt_MoveCursor(13,2);
        print(pulseRate);   // Print pulserate transparently on heart icon
    else
        txt_Set(TEXT_COLOUR, CYAN); // Erase pulserate with opaque panel color
        txt_Set(TEXT_OPACITY, OPAQUE);
        txt_Set(TEXT_HIGHLIGHT, PANEL_COLOR);
        txt_MoveCursor(13,2);
    print("    ");                  // Erase pulserate with opaque panel color
    txt_MoveCursor(13,2);
    print(pulseRate);               // Print pulserate with opaque panel color
    endif

endfunc

func showHeartWithBeep()
    if(sdPresent && file_Exists("ding.wav"))
        snd_Pitch(15000<<2);
        file_PlayWAV("ding.wav");                   // Lack of mem may cause this to fail
    endif
    showHeart();
endfunc

func showHeart()
    gfx_CircleFilled(16,157,7,HEART_ICON_COLOR);    // Draw the heart icon
    gfx_CircleFilled(31,157,7,HEART_ICON_COLOR);
    gfx_CircleFilled(24,159,3,HEART_ICON_COLOR);
    gfx_TriangleFilled(10,161,38,161,24,179,HEART_ICON_COLOR);
endfunc

func hideHeart()
    gfx_RectangleFilled(9,148,38,179, PANEL_COLOR);
endfunc

/********************************************************
  Get a real sample, or simulated sample if mode = sim
********************************************************/
func getSample()
    var tempval;
    if(!paused)
        if (mode == SIM_MODE)
            getSimulatedSample();   // Get simulated data sample
        else
            readSample();           // Get real sample from ADC
        endif

        // Assemble r1 and r2 into an integer value
        //  scale value and add offset for display

        tempval := ( (r2 + ((r1 << 8)))/GRAPH_SCALE_DIVISOR) + GRAPH_Y_OFFSET;
        if(tempval > GRAPH_MAX_Y)  tempval := GRAPH_MAX_Y;
        if(tempval < GRAPH_MIN_Y)  tempval := GRAPH_MIN_Y;
        signalBuffer[dataInIndex++]:= tempval;      // Store data for later display
        if(dataInIndex == SIGNAL_BUFFER_LEN)
            dataInIndex := 0;                       // Wrap around to beginning of buffer
        endif
    endif
    sys_SetTimer(DATA_SAMPLE_TIMER,SAMPLE_INTERVAL);           // Reset timer for next tick
endfunc

/*********************************************************
  Simulate a signal by adjusting r1,r2 to create pulses
*********************************************************/
func getSimulatedSample()
    simIndex++;
    r2 := 150;
    r1 := 4;
    if((simIndex >= STARTR) && (simIndex < ENDR))    r1 := 12;
    if((simIndex >= STARTT) && (simIndex < ENDT))    r1 := 8;
    if(simIndex == ENDT)
        r1 := 4;
        simIndex := 0;
    endif
endfunc


/**********************
Configure the ADC chip
***********************/
func initADC()
#IF (FRONT_END == 1.3)
    adcConfigByte := 0b00111000;  /* channel 0 and 1, Reference=ch3/REF, SDA and SCL are Filtered */
#ELSE
    adcConfigByte := 0b00010000;  /* channel 0 only, Reference=Vdd, SDA and SCL are Filtered */
#ENDIF

    I2C_Open(I2C_MED);              // Medium speed (slow or fast work also)
    I2C_Idle();                     // Wait for bus Idle
    I2C_Start();                    // Start
    I2C_Write(ADC_WRITE);           // Send control byte for write
    I2C_Idle();                     // Wait for bus Idle
    I2C_Write(adcConfigByte);          // Send Config byte
    I2C_Stop();
endfunc

/**********************
  Pull a sample from
  the ADC chip
***********************/
func readSample()
    r1 := 0;
    r2 := 0;
    r3 := 0;
    r4 := 0;
    I2C_Idle();                     // Wait for bus Idle
    I2C_Start();                    // Start
    I2C_Write(ADC_READ);            // Send control byte for Read
    I2C_Idle();                     // Wait for bus Idle
    r1 := I2C_Read();               // Read ch0 Data    NOTE: If ADC does not respond, it may hang here !
    I2C_Ack();
    r2 := I2C_Read();

#IF (FRONT_END == 1.3)
    I2C_Ack();
    r3 := I2C_Read();               // Read ch1 data
    I2C_Ack();
    r4 := I2C_Read();
#ENDIF

    I2C_Nack();                     // Send Not Ack to end transaction
    I2C_Stop();
endfunc

/**********************
  Prepare the display
***********************/
func initScreen()
    gfx_Set(SCREEN_MODE,LANDSCAPE_R);
    txt_Set(TEXT_COLOUR, YELLOW);
    txt_Set(FONT_SIZE, FONT3);
    txt_Set(TEXT_OPACITY, TRANSPARENT);
    txt_Set(TEXT_HIGHLIGHT, PANEL_COLOR);
    gfx_Set(BEVEL_WIDTH,2);
    gfx_Set(BEVEL_SHADOW,2);
    gfx_Cls();
    gfx_Panel(PANEL_RAISED, GRAPH_MIN_X-2, GRAPH_MAX_Y+8, GRAPH_MAX_X+5, PANEL_HEIGHT, PANEL_COLOR);
    gfx_Rectangle(GRAPH_MIN_X+55,PANEL_BOTTOM_Y-20,GRAPH_MIN_X+240,PANEL_BOTTOM_Y,PANEL_BORDER_COLOR);
    gfx_Button(UP, OFF_BUTTON_X, OFF_BUTTON_Y, OFF_BUTTON_COLOR, WHITE, FONT3, 1, 1, "OFF");
    gfx_Button(UP, MODE_BUTTON_X, MODE_BUTTON_Y, MODE_BUTTON_COLOR, WHITE, FONT3, 1, 1, " SIM ");
    gfx_Button(UP, SCALE_BUTTON_X, SCALE_BUTTON_Y, TIMESCALE_BUTTON_COLOR, WHITE, FONT3, 1, 1, " TS1 ");
    gfx_Button(UP, PAUSE_BUTTON_X, PAUSE_BUTTON_Y, PAUSE_BUTTON_COLOR, BLACK, FONT3, 1, 1, "PAUSE");
    gfx_Button(UP, SCREENCAP_BUTTON_X, SCREENCAP_BUTTON_Y, SCREENCAP_BUTTON_COLOR, WHITE, FONT3, 1, 1, " SCR ");
    gfx_Button(UP, FILE_BUTTON_X, FILE_BUTTON_Y, FILE_BUTTON_COLOR, WHITE, FONT3, 1, 1, str_Ptr(filename));
    gfx_Button(UP, RECORD_BUTTON_X, RECORD_BUTTON_Y, RECORD_BUTTON_COLOR, WHITE, FONT3, 1, 1, "RECORD");
    gfx_Button(UP, RECALL_BUTTON_X, RECALL_BUTTON_Y, RECALL_BUTTON_COLOR, WHITE, FONT3, 1, 1, "RECALL");
    leftPageButtonShow(PAGE_BUTTON_COLOR);
    rightPageButtonShow(PAGE_BUTTON_COLOR);
    updateFileSlider(100, 1);
    initGraph();
    showTimescale();
    showMode();
    showPaused();
endfunc


/*********************************************************************
  Buttons and slider for paging forward or backward thru a data file
***********************************************************************/
func updateFileSlider(var range, var pos)
    gfx_Set(OBJECT_COLOUR, PAGE_THUMB_COLOR);
    fileSlider :=  gfx_Slider(1,PAGE_SLIDER_X1,PAGE_SLIDER_Y1,PAGE_SLIDER_X2, PAGE_SLIDER_Y2, PAGE_SLIDER_COLOR,range,pos);
    return fileSlider;
endfunc

func leftPageButtonShow(var color)
    gfx_TriangleFilled(LEFT_PAGE_BUTTON_X,LEFT_PAGE_BUTTON_Y-21,LEFT_PAGE_BUTTON_X,LEFT_PAGE_BUTTON_Y,LEFT_PAGE_BUTTON_X-10,LEFT_PAGE_BUTTON_Y-10,color);
endfunc

func rightPageButtonShow(var color)
    gfx_TriangleFilled(RIGHT_PAGE_BUTTON_X,RIGHT_PAGE_BUTTON_Y-21,RIGHT_PAGE_BUTTON_X,RIGHT_PAGE_BUTTON_Y,RIGHT_PAGE_BUTTON_X+10,RIGHT_PAGE_BUTTON_Y-10,color);
endfunc

func leftPageButtonFlash()
    leftPageButtonShow(BLACK);
    pause(100);
    leftPageButtonShow(PAGE_BUTTON_COLOR);
endfunc

func rightPageButtonFlash()
    rightPageButtonShow(BLACK);
    pause(100);
    rightPageButtonShow(PAGE_BUTTON_COLOR);
endfunc

func recallButtonFlash()
   gfx_Button(UP, RECALL_BUTTON_X, RECALL_BUTTON_Y, RECALL_BUTTON_COLOR, BLACK, FONT3, 1, 1, "RECALL");
   pause(100);
   gfx_Button(UP, RECALL_BUTTON_X, RECALL_BUTTON_Y, RECALL_BUTTON_COLOR, WHITE, FONT3, 1, 1, "RECALL");
endfunc

/******************************************
  Initialize the graph area of the display
  ****************************************/
func initGraph()
    gfx_Rectangle(GRAPH_MIN_X-1,GRAPH_MIN_Y-1,GRAPH_MAX_X+4,GRAPH_MAX_Y+2,GRAPH_BORDER_COLOR);
    gfx_Rectangle(GRAPH_MIN_X-2,GRAPH_MIN_Y-2,GRAPH_MAX_X+5,GRAPH_MAX_Y+3,GRAPH_BORDER_COLOR);
    gfx_RectangleFilled(GRAPH_MIN_X,GRAPH_MIN_Y,GRAPH_MAX_X,GRAPH_MAX_Y,GRAPH_BACKGROUND_COLOR);
    graphX := GRAPH_MIN_X +1;
    graphY := GRAPH_MAX_Y -1;
    gfx_ObjectColour(GRAPH_PLOT_COLOR);
endfunc

/****************************************************
  Setup a timer to driver sampling of signal
****************************************************/
func initSampleTimer()
    sys_SetTimerEvent(DATA_SAMPLE_TIMER, getSample);
    sys_SetTimer(DATA_SAMPLE_TIMER,SAMPLE_INTERVAL);
endfunc


/****************************************************
  Setup a timer to update battery status indicator
****************************************************/
func initBatteryMonitorTimer()
    sys_SetTimerEvent(BATTERY_MONITOR_TIMER, showBatteryVoltage);
    sys_SetTimer(BATTERY_MONITOR_TIMER,BATTERY_INTERVAL);
endfunc

/**********************************************
  Display a battery status indicator
    showing the voltage and using
    color to indicate good, marginal, or low
**********************************************/
func showBatteryVoltage()
    var integer;
    var fraction;
    var indicatorColor;
    var textColor;
    if(mode == SIM_MODE)  readSample();  // If in SIM mode, we must initiate an ADC read
    integer := ((((r3<<8) + r4)/BATTERY_COUNTS_PER_TENTH_VOLT)/10);
    fraction := (((r3<<8) + r4)/BATTERY_COUNTS_PER_TENTH_VOLT) - (integer * 10);
    txt_Set(FONT_SIZE, FONT2);
    txt_Set(TEXT_COLOUR, WHITE);
    txt_MoveCursor(24,1);
    indicatorColor := LIGHTGREEN;        // Default green for good battery
    textColor := BLACK;
    if ((integer == 5) && (fraction < 2))
        indicatorColor := GOLD;         // Yellow for marginal battery
        textColor := BLACK;
    endif
    if ((integer < 5))
        indicatorColor := CRIMSON;      // Red for low battery
        textColor := WHITE;
    endif
    txt_Set(TEXT_COLOUR, textColor);    // Render the voltage monitor
    txt_Set(TEXT_HIGHLIGHT, indicatorColor);
    gfx_RectangleFilled(5,190,40,200,indicatorColor);
    print(integer,".",fraction,"v");
    initBatteryMonitorTimer();          // Re-enable the timer
endfunc

/****************************
    Initialize  variables
*****************************/
func initParameters()
    dataInIndex := RTRIG_LOOK_AHEAD;  // Setup indices for data ring buffer
    dataOutIndex := 0;
    paused := 0;
    mode := 3;              // Change this if you want to start up on a different mode
    timescale := 1;         // Change this if you want to start up on a different timescale
    timescaleIndex := 2;
    simIndex := 0;
    r1 := 0;                // Data input bytes
    r2 := 0;
    r3 := 0;
    r4 := 0;
    rTriggerLevel := 11;     // If r1 reaches this level, an R wave is indicated
    rTriggerGraphY := ((rTriggerLevel << 8)/GRAPH_SCALE_DIVISOR) + GRAPH_Y_OFFSET;
    OKtoRewind := 0;
    currentFile := 0;
    to(filename); print("File", currentFile);
endfunc

/*******************************************************************
  Cycle thru names of files that are available for recording data
********************************************************************/
func rotateFile()
    currentFile++;
    if(currentFile == MAX_FILES)
        currentFile := 0;
    endif

    showFile();
endfunc

/*************************************
  Display current file on file button
**************************************/
func showFile()
     gfx_Button(DOWN, FILE_BUTTON_X, FILE_BUTTON_Y, FILE_BUTTON_COLOR, BLACK, FONT3, 1, 1, str_Ptr(filename));
     to(filename); print("File", currentFile);
     pause(70);
     gfx_Button(UP, FILE_BUTTON_X, FILE_BUTTON_Y, FILE_BUTTON_COLOR, WHITE, FONT3, 1, 1, str_Ptr(filename));
endfunc

/*************************************
  Cycle thru timescales available
**************************************/
func changeTimescale()
    var previousTimeScale;
    previousTimeScale := timescale;
    timescale++;
    if(timescale > 4)
        timescale := 1;
    endif
    initGraph();
    showTimescale();
    if(fileHandle) updatePages(previousTimeScale);  // Re-calculate number of pages in any open file
endfunc

func showTimescale()
    if(timescale == 1) gfx_Button(UP, SCALE_BUTTON_X, SCALE_BUTTON_Y, TIMESCALE_BUTTON_COLOR, WHITE, FONT3, 1, 1, " TS1 ");
    if(timescale == 2) gfx_Button(UP, SCALE_BUTTON_X, SCALE_BUTTON_Y, TIMESCALE_BUTTON_COLOR, WHITE, FONT3, 1, 1, " TS2 ");
    if(timescale == 3) gfx_Button(UP, SCALE_BUTTON_X, SCALE_BUTTON_Y, TIMESCALE_BUTTON_COLOR, WHITE, FONT3, 1, 1, " TS3 ");
    if(timescale == 4) gfx_Button(UP, SCALE_BUTTON_X, SCALE_BUTTON_Y, TIMESCALE_BUTTON_COLOR, WHITE, FONT3, 1, 1, " TS4 ");
endfunc

/**********************************
 Cycle thru the available modes
  1 = Continous
  2 = Trigger on R wave
  3 = Simulate
***********************************/
func changeMode()
    if(mode == 1)
        mode := 2;
    else
        if(mode == 2)
            mode := 3;
        else
            if(mode == 3)
                mode := 1;
            endif
        endif
    endif
    showMode();
    initGraph();
endfunc

func showMode()
    if(mode == RTRIG_MODE)
        gfx_Button(UP, MODE_BUTTON_X, MODE_BUTTON_Y, MODE_BUTTON_COLOR, WHITE, FONT3, 1, 1, " TRG ");
        OKtoRewind := 0;
    endif
    if(mode == SIM_MODE)    gfx_Button(UP, MODE_BUTTON_X, MODE_BUTTON_Y, MODE_BUTTON_COLOR, WHITE, FONT3, 1, 1, " SIM ");
    if(mode == CONT_MODE)   gfx_Button(UP, MODE_BUTTON_X, MODE_BUTTON_Y, MODE_BUTTON_COLOR, WHITE, FONT3, 1, 1, " CON ");
endfunc

/********************
  Toggle Pause button
********************/
func togglePause()
    if(!paused)
        paused := 1;
    else
        paused := 0;
    endif
    showPaused();
endfunc

func showPaused()
 if(paused)
        gfx_Button(DOWN, PAUSE_BUTTON_X, PAUSE_BUTTON_Y, PAUSE_BUTTON_COLOR, BLACK, FONT3, 1, 1, " RUN ");
    else
        gfx_Button(UP, PAUSE_BUTTON_X, PAUSE_BUTTON_Y, PAUSE_BUTTON_COLOR, BLACK, FONT3, 1, 1, "PAUSE");
        clearMessage();
    endif
endfunc

/******************************
  Save screen image to a file
*******************************/
func saveScreenShot()
    if(!recordingToFile)
        if(!paused) togglePause();
        gfx_Button(DOWN, SCREENCAP_BUTTON_X, SCREENCAP_BUTTON_Y, SCREENCAP_BUTTON_COLOR, WHITE, FONT3, 1, 1, " SCR ");
        ScrGrab_Init(TRUE, 0);
        ScrGrab(SCREEN_HRES, SCREEN_VRES);
        clearMessage();
        gfx_Button(UP, SCREENCAP_BUTTON_X, SCREENCAP_BUTTON_Y, SCREENCAP_BUTTON_COLOR, WHITE, FONT3, 1, 1, " SCR ");
        if(paused) togglePause();
    endif
endfunc

/********************************
  Show an informative message
*********************************/
func setMessage(var text)
    txt_Set(TEXT_COLOUR, YELLOW);
    txt_Set(FONT_SIZE, FONT3);
    txt_Set(TEXT_OPACITY, OPAQUE);
    clearMessage();
    txt_MoveCursor(MESSAGE_LINE,MESSAGE_COLUMN);
    txt_Set(TEXT_HIGHLIGHT, PANEL_COLOR);
    putstr(text);
endfunc

func clearMessage()
    txt_Set(TEXT_COLOUR, YELLOW);
    txt_Set(TEXT_OPACITY, OPAQUE);
    txt_Set(TEXT_HIGHLIGHT, PANEL_COLOR);
    txt_Set(FONT_SIZE, FONT3);
    txt_MoveCursor(MESSAGE_LINE,MESSAGE_COLUMN);
    putstr("                      ");
endfunc

/******************************************************************************
 Screen saver facility
   You must call initScreenTimer to activate, and call screenTimerCheck() in main loop
*******************************************************************************/
func screenTimerCheck()
    if(sys_GetTimer(SCREENSAVER_TIMER) == 0)
        screenSaver();
        initScreenTimer();
    endif
endfunc

func initScreenTimer()
    sys_SetTimer(SCREENSAVER_TIMER,SCREENTIMEOUT);
endfunc

func screenSaver()
  var line, col;
  txt_Set(TEXT_COLOUR, CYAN);
  txt_Set(FONT_SIZE, FONT3);
  gfx_Cls();
  sys_SetTimer(SCREENSAVER_TIMER,2000);
  repeat
    state := touch_Get(TOUCH_STATUS);
    if (state == TOUCH_PRESSED)  break;
    if(sys_GetTimer(SCREENSAVER_TIMER) == 0)
       line := (ABS(RAND())/2000);
       col := (ABS(RAND())/2000) + 10;
       gfx_Cls();
       txt_MoveCursor(line,col);
       print("Touch me");
       sys_SetTimer(SCREENSAVER_TIMER,2000);
    endif
  forever
  while (touch_Get(TOUCH_STATUS) != TOUCH_RELEASED);
  initScreen();
endfunc

/*****************************************************************
 Screen grab utility By Steve

 Requires RawConv.exe to convert the saved files to .bmp format
     see http://4d.websitetoolbox.com/post?id=3122887

  enable = TRUE to enable screen grabs, FALSE to disable
******************************************************************/
func ScrGrab_Init(var enable, var num)
    grab_enable := enable;
    grab_num := num % 100000;
endfunc

/**********************************
 Screen Grab

 Save current screen to file
  w = width of screen
  h = height of screen
**********************************/
func ScrGrab(var w, var h)
    var bpfname, bpnum, hnd, x, y, tmp;
    if(grab_enable)
        file_Erase("SCRIMG.RAW");
        hnd := file_Open("SCRIMG.RAW", 'w');
        if(hnd == 0)
            txt_MoveCursor(MESSAGE_LINE,MESSAGE_COLUMN);
            print("ERR RC=", file_Error(), "\n");
            pause(3000);
        else
            file_PutW(0x4690, hnd);     // File sig.
            file_PutW(w, hnd);          // Image width
            file_PutW(h, hnd);          // Image height
            y := 0;
            repeat
                x := 0;
                repeat
                    // Save a pixel colour to file
                    file_PutW(tmp:=gfx_GetPixel(x, y), hnd);
                    // Display optional progress indicator
                    gfx_PutPixel(x, y, 0xFFFF); gfx_PutPixel(x, y, tmp);
                until(++x >= w);
            until(++y >= h);
            file_Close(hnd);
        endif
    endif
endfunc





